<?php

/**
 * @name 网页二维码扫码服务支持
 * @author vipkwd <service@vipkwd.com>
 * @link https://github.com/wxy545812093/vipkwd-phputils
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @copyright The PHP-Tools
 */

declare(strict_types=1);

namespace Vipkwd\Utils\Scan;

use Vipkwd\Utils\Image\Qrcode as VkQrcode;
use Vipkwd\Utils\Image\Thumb as VkThumb;
// use Vipkwd\Utils\Ip as VkIP;
use Vipkwd\Utils\Http as VKHttp;
use Vipkwd\Utils\Wx\Mp\User\User as MpUser;
use Vipkwd\Utils\SocketPush\WebPusher;
use Vipkwd\Utils\System\Cookie;

class WebLogin // extends WebLoginMPAbstract
{
    private static $_request = null;
    private $_signFields = [];
    private $_query = [];

    private $_mpAppName = '';
    private $_mpAppId = '';
    private $_mpAppSecret = '';

    private $_pushApiUrl = '';

    public function __construct(array $options = [])
    {
        $this->query();
        if (isset($options['mp_app_name'])) {
            $this->_mpAppName = trim($options['mp_app_name']);
        }
        if (isset($options['mp_app_id'])) {
            $this->_mpAppId = trim($options['mp_app_id']);
        }
        if (isset($options['mp_app_secret'])) {
            $this->_mpAppSecret = trim($options['mp_app_secret']);
        }
        if (isset($options['web_push_url'])) {
            $this->_pushApiUrl = urldecode($options['web_push_url']);
        }
        $this->_signFields = array_merge(['userId', 'clientId', 'qrcodeId'], $options['signFields'] ?? []);
    }

    /**
     * [绑定账户] 生成二维码
     */
    public function bindQrcodeCreate(array $params = [])
    {
        $params = array_merge(['userId' => '', 'sid' => '', 'clientId' => '', 'qrcodeId' => '', 'redirect' => '/'], $params, $this->_query);
        $signText  = 'bind.';
        foreach ($this->_signFields as $field) {
            $signText .= ($params[$field] ?? '');
        }

        if ($this->query('qrcodeId', '') !== '') {
            $params['qrcodeId'] = $this->query('qrcodeId', '');
        } else if ($this->query('xcode', '') !== '') {
            $params['qrcodeId'] = $this->query('xcode', '');
        }

        if (!$params['clientId'] || !$params['qrcodeId']) {
            header('Content-type: image/jpeg');
            VkThumb::instance()->createPlaceholder("200x200", 1, 1, 15, "码图身份ID缺失");
        }
        // $params['ip'] = VkIP::getClientIp();
        // $params['text'] = $signText;
        // $params['sign'] = md5(md5($signText) . 'adminBindGzhQrcode' . VkIP::getClientIp());
        $params['sign'] = $this->createBindGZHSignKey($params);

        $redirect = $this->createRedirectUrl($params['redirect']);
        unset($params['redirect']);

        // VkQrcode::make(request()->domain() . "/bindWxGzh/storeManager?userId={$params['userId']}&sid={$params['userId']}&bindKey={$hash}", false, '30%');
        VkQrcode::make($redirect . '?' . http_build_query($params), false, '30%');
    }

    /**
     * [绑定账户] 微信扫码后跳转的方法
     * @param closure $fetchUserOpenIdWithUid
     * 
     * @return array|void 返回数组表示本地绑定关系需要刷新
     */
    public function bindQrcodeValidate(\Closure $fetchUserOpenIdWithUid)
    {
        $params = array_merge(['userId' => ''], $this->_query);

        // 关闭验证，防止微信侧 重定向过来 因IP不对等，验证不通过的问题
        if (!isset($params['state']) && !isset($params['code'])) {
            if ($params['sign'] != $this->createBindGZHSignKey($params)) {
                MpUser::followFailView('此码已失效');
                exit;
            }
        }

        $gzhOpenId = $fetchUserOpenIdWithUid($params['userId']);
        $instance = MpUser::instance($this->_mpAppId, $this->_mpAppSecret);
        //已关注
        if ($gzhOpenId) {
            //没有关注公众号
            if (false === $instance->isFollow($gzhOpenId)) {
                //清空原始授权code(原因可能是： 用户关注公众号后，成功获取openID后，又取关了公众号)
                return [
                    'openId' => '',
                    'userId' => $params['userId'],
                    'view' =>  MpUser::followFailView('绑定失败<p>请先搜索关注微信公众号 "' . $this->_mpAppName . '" </p>', 'return')
                ];
            }
            MpUser::followSuccessView('绑定成功');
            exit;
        }

        //绑定
        $redirect_url = '?' . http_build_query($params);
        $res = $instance->getOpenId($redirect_url);
        if (array_key_exists('openid', $res)) {
            if (strlen($res['openid']) > 12) {
                //清空原始授权code(原因可能是： 用户关注公众号后，成功获取openID后，又取关了公众号)
                if (false !== $instance->isFollow($res['openid'])) {
                    return [
                        'openId' => $res['openid'],
                        'userId' => $params['userId'],
                        'view' => MpUser::followSuccessView()
                    ];
                }
                MpUser::followFailView('绑定失败<p>请先搜索关注微信公众号 "' . $this->_mpAppName . '" </p>');
                exit;
            }
        }
        MpUser::followFailView('订阅/绑定失败，请退回重新尝试');
        exit;
    }

    /**
     * [扫码登录] 生成二维码
     * @param array $params
     * @return \header
     */
    public function loginQrcodeCreate(array $params = [])
    {
        $this->_query['scene_tag'] = '网页管理端';
        $redirect = $this->createRedirectUrl($params['redirect']);

        unset($params['redirect']);
        $params = $this->loginQrcodeSign(false, $params, $this->_query);

        VkQrcode::make($redirect . '?' . http_build_query($params), false, '30%');
    }

    /**
     * [扫码登录] 已扫码等待确认
     * @param string $redirect
     * @return void
     */
    public function loginQrcodeValidate(string $redirect)
    {

        $expired_secends = 100 * 60; //10分钟
        $params = $this->loginQrcodeSign(true, $this->_query);

        // devdump([$this->_query, $params, self::$_request], 1);
        $params = array_merge(['scene_tag' => '客户端'], $params);
        if ($params['sign'] == $params['oldSign']) {

            if (($params['time'] + $expired_secends) > time()) {

                if ($params['btnConfirm']) {
                    // 数据库验证成功
                    if ($params['dbConfirm']) {

                        MpUser::followSuccessView('授权成功，请继续' . $params['scene_tag'] . '操作!');

                        //通知页面授权结果
                        // list($userId, $storeId) = explode('-', $params['dbConfirm']);

                        // $data = [
                        //     'userId' => $userId,
                        //     'storeId' => $storeId,
                        //     // 'time' => time()
                        // ];
                        if ($this->_pushApiUrl) {
                            $this->pushWebMsg($params['clientId'], $params['qrcodeId'], 'confirm', [
                                'text' => '',       //扫码确认后，服务端识别微信用户 与 应用侧用户身份对应关系后，供业务侧登录接口“登录”验证的加密字符串（具体加密方案需要：此处与业务侧对等，即此处加密，业务侧可解密）
                                'formData' => '',   //扫码确认后，服务端识别微信用户 与 应用侧用户身份对应关系后，供业务侧登录接口“登录”验证的数组（可不加密，也可加密；加密时参见前述方案）
                            ]);
                        }
                        return;
                    }
                    // 跳转微信获取OPENID , 并落库验证
                    return header('location: ' . $this->createRedirectUrl($redirect) . '?' . http_build_query([
                        'clientId' => $params['clientId'],
                        'time' => $params['time'],
                        'sign' => $params['sign'],
                        'qrcodeId' => $params['qrcodeId'],
                        'confirm' => true,
                        'scanerValidator' => 1,
                        'scene_tag' => $params['scene_tag']
                    ]));
                }

                //通知页面扫码结果
                if ($this->_pushApiUrl) {
                    $this->pushWebMsg($params['clientId'], $params['qrcodeId'], 'complete');
                }

                $message  = '<a href="' . self::$_request->url_path . '?' . (http_build_query($this->_query)) . '&confirm=true">';
                $message .= '<h4 class="weui_msg_title" style="border: 1px solid #008dff; background: #008dff; color: #fff; padding: 10px; border-radius: 100px; margin-top: 4rem; display: flex; align-items: center; justify-content: center;">确认登录' . $params['scene_tag'] . '</h4>';
                $message .= '</a>';
                MpUser::wxBrowserMessage($message, 'success', true);
                return;
            }
        }

        return MpUser::wxBrowserMessage('客户端失效', 'safe_warn');
    }

    /**
     * [扫码登录] 微信授权重定向方法
     * @param closure $fetchUserIdWithOpenId
     * 
     * @return void
     */
    public function loginQrcodeConfirm(\Closure $fetchUserIdWithOpenId)
    {

        $params = array_merge(['scanerValidator' => ''], $this->_query);

        // devdump(self::$_request, 1);

        if ($params['scanerValidator'] > 0) {
            Cookie::set('scanerValidator', $params['scanerValidator']);
        } else {
            $params['scanerValidator'] = Cookie::get('scanerValidator', 0);
        }

        if ($params['scanerValidator'] <= 0) {
            MpUser::followFailView('身份认证失败');
            exit;
        }

        $instance = MpUser::instance($this->_mpAppId, $this->_mpAppSecret);
        //绑定
        //使用当前页面接口接受微信重定向
        $redirect_url = self::$_request->url_path . '?' . http_build_query($params);
        $res = $instance->getOpenId($redirect_url);
        if (array_key_exists('openid', $res)) {
            if (strlen($res['openid']) > 12) {

                //没有关注公众号
                if (false === $instance->isFollow($res['openid'])) {
                    MpUser::followFailView('授权失败<p>请先搜索关注微信公众号 "' . $this->_mpAppName . '" </p>');
                    exit;
                }
                $localOpenUserId = $fetchUserIdWithOpenId($res['openid']);
                //存在管理员ID
                if ($localOpenUserId) {
                    $referrer = explode('?', self::$_request['referrer']);
                    $referrer = array_shift($referrer);
                    return header('location: ' . $referrer . '?' . http_build_query([
                        'clientId' => $params['clientId'],
                        'qrcodeId' => $params['qrcodeId'],
                        'time' => $params['time'],
                        'sign' => $params['sign'],
                        'confirm' => true,
                        'dbConfirm' => implode('-', [$localOpenUserId, ''])
                    ]));
                }
                MpUser::followFailView('当前微信号没有绑定商户管理员');
                exit;
            }
        }
        MpUser::followFailView('请在微信小程序内使用此功能');
        exit;
    }

    /**
     * 推送自定义网页socket通知
     * 
     * @param string $clientId 二维码展示网页监听的 socketId(身份标识)
     * @param string $qrcodeId 展示在网页的那张二维码的ID(身份标识)，源网页接收推送消息的时候应该鉴别qrcodeId
     * @param string $websocketUrl websocket网关地址
     * @param string $type <normal>推送事件标识 [ complete/fail/confirm/cancel/其他自定义事件标识 ]
     * @param array $data 推送自定义数据
     * 
     * @return boolean
     */
    protected function pushWebMsg(string $clientId, string $qrcodeId, string $type = 'normal', array $data = [])
    {
        $res = 'fail';
        if ($this->_pushApiUrl) {
            $data['clientId'] = $clientId;
            $data['qrcodeId'] = $qrcodeId;
            $event = ['complete', 'fail', 'confirm', 'cancel'];
            $data['scan_state'] = in_array($type, $event) ? $type : ($type ? $type : 'normal');
            $res = WebPusher::instance($this->_pushApiUrl)->to($clientId)->data($data)->get();
        }
        return $res == 'ok';
    }

    private function createBindGZHSignKey(array $params, string $salt = 'adminBindGzhQrcode')
    {
        $signText  = [];
        ksort($this->_signFields);
        foreach ($this->_signFields as $field) {
            $signText[$field] = urlencode($params[$field] ?? '');
        }
        // return md5(md5($signText) . 'adminBindGzhQrcode' . VkIP::getClientIp());
        return md5(md5(json_encode($signText)) . $salt);
    }

    private function loginQrcodeSign(bool $verify = false, array $params = [], array $extendParams = [])
    {
        if (empty($params)) {
            $params = [
                'time' => time(),
                'clientId' => $this->query('clientId', '')
            ];
        }
        if (!isset($params['time'])) {
            $params['time'] = time();
        }
        if ($this->query('qrcodeId', '') !== '') {
            $params['qrcodeId'] = $this->query('qrcodeId', '');
        } else if ($this->query('xcode', '') !== '') {
            $params['qrcodeId'] = $this->query('xcode', '');
        }
        $params = array_merge($extendParams, $params);

        $oldSign = null;
        $btnConfirm = null;
        $dbConfirm = null;
        $scanerValidator = null;
        if ($verify === true) {
            foreach (array_keys($params) as $field) {
                if ($field == 'sign') {
                    $oldSign = $params['sign'];
                    unset($params['sign']);
                } else if ($field == 'confirm') {
                    $btnConfirm = $params['confirm'];
                    unset($params['confirm']);
                } else if ($field == 'dbConfirm') {
                    $dbConfirm = $params['dbConfirm'];
                    unset($params['dbConfirm']);
                } else if ($field == 'scanerValidator') {
                    $scanerValidator = $params['scanerValidator'];
                    unset($params['scanerValidator']);
                } else {
                    $params[$field] = urldecode(strval($params[$field]));
                }
            }
        }
        ksort($params);
        $params['sign'] = md5(md5(http_build_query($params)) . 'scanLoginImage');
        if ($oldSign) {
            $params['btnConfirm'] = $btnConfirm;
            $params['dbConfirm'] = $dbConfirm;
            $params['oldSign'] = $oldSign;
            $params['scanerValidator'] = $scanerValidator;
        }
        return $params;
    }

    private function createRedirectUrl(string $redirect)
    {
        if (strripos($redirect, 'http') !== 0) {
            $redirect = self::$_request->domain . '/' . ltrim($redirect, '/');
        }
        return $redirect;
    }

    private function query(string $key = '', $default = null)
    {
        if (empty($this->_query)) {
            self::$_request = VKHttp::request();
            $query = array_values((array)self::$_request->query);
            $query = array_shift($query);
            $this->_query = $query;
            unset($query);
        }
        if ($key) {
            return isset($this->_query[$key]) ? $this->_query[$key] : $default;
        }
        return $this->_query;
    }
}
